/* eslint-disable @typescript-eslint/indent */
import { NotifyService } from '@farris/ui-notify';
import { map, catchError } from 'rxjs/operators';
import {
    FormBasicService, DomService, WebCommandMetadata, WebCommand,
     FarrisMetadataDto, WebCommandMetadataConvertor
} from '@farris/designer-services';
import { Case } from '../entity/case';
import { ExecuteNode, OperationExtendLevel } from '../entity/execute-node';
import { Extension } from '../entity/extension';
import { OperationCollection } from '../entity/operation-collection';
import { ParamConfig } from '../entity/param';
import { SwitchNode } from '../entity/switch-node';
import { ITreeNode } from '../entity/tree-node';
import { Command } from '../entity/command';
import { BehaviorSubject, ReplaySubject, Observable, of, forkJoin } from 'rxjs';
import { Injectable } from '@angular/core';
import { Operation, WebComponentMetadata } from '../entity/webcmp-metadata';
import { IdService } from '@farris/ui-common';
import { MessagerService } from '@farris/ui-messager';
import { DesignerMetadataService } from '../../../service/designer-metadata.service';

@Injectable()
export class ActionsBuilder extends BehaviorSubject<Command[] | any> {
    private commands: Command[] | any[];

    private webcmds: ReplaySubject<WebCommandMetadata[]>;

    private webcmps: ReplaySubject<WebComponentMetadata[]>;

    constructor(
        private domService: DomService,
        private formBasicService: FormBasicService,
        private metadataService: DesignerMetadataService,
        private msgService: MessagerService,
        private notifyService: NotifyService,
        private idService: IdService
    ) {
        super([]);
        this.commands = [];
        this.webcmds = new ReplaySubject(1);
        this.webcmps = new ReplaySubject(1);
    }

    public build(commandsJson: any) {
        this.loadWebcmd().subscribe(webcmds => {
            this.commands = [];
            commandsJson.forEach(commandJson => {
                if (commandJson.isInvalid) {
                    const node = {
                        data: commandJson,
                        selectable: true,
                        code: commandJson.code,
                        cmpId: commandJson.cmpId,
                        isInValid: true
                    } as any;
                    this.commands.push(node);
                    return;
                }
                const webcmd = webcmds.find(item => item.Id === commandJson.cmpId);
                if (!webcmd) {
                    return;
                }
                const webCommand = webcmd.Commands.find(item => item.Code === commandJson.handlerName);
                if (!webCommand) {
                    const node = {
                        data: commandJson,
                        selectable: true,
                        id: commandJson.id,
                        code: commandJson.code,
                        cmpId: commandJson.cmpId,
                        isInValid: true
                    } as any;
                    this.commands.push(node);
                    return;
                }
                const command = new Command(webCommand, commandJson, webcmd);

                // 唯一化id，如果添加多个相同命令，操作的id会重复，树节点开不开。
                this.recursiveUniqueId(command.children);

                this.commands.push(command);

                // 记录命令的参数是否需要刷新
                if (command.params.length) {
                    const disusedParam = command.params.find(param => param.isDisused);
                    command.needRefreshParam = !!disusedParam;
                }
            });

            this.next(this.commands);
        }, err => {
            // 若使用this.error 会导致流的中断，故使用next并增加flag标识
            this.next({
                flag: 'error',
                msg: err
            });
        });
    }

    /**
     * 唯一化操作的id。如果添加多个相同命令，操作的id会重复，树节点的展开有问题
     */
    private recursiveUniqueId(arr: any[]) {
        arr.forEach(item => {
            item.id = this.idService.generate();
            if (item.children && item.children.length) {
                this.recursiveUniqueId(item.children);
            }
        });
    }

    public addCommand(webCommand: WebCommand, cmp: WebCommandMetadata, viewModelId: string) {
        const command = new Command(webCommand, cmp.Id, cmp);
        command.id = this.idService.generate();

        this.recursiveUniqueId(command.children);

        // 处理code、name
        this.refreshCommandCodeName(command, viewModelId);

        this.commands.push(command);
        this.next([...this.commands]);
        return command;
    }

    public removeCommand(command: Command) {
        const index = this.commands.findIndex(item => item.id === command.id);
        this.commands.splice(index, 1);
        this.next([...this.commands]);
    }

    public addExtension(type: number, parent: ExecuteNode) {
        if (!parent) {
            return;
        }

        let msg;
        let extension: Extension;
        switch (type) {
            case 1:
                if (!parent.preExtendable) {
                    msg = '构件不允许正在编辑的节点进行操作前扩展。';
                    break;
                } else if (parent.preExtension) {
                    msg = '正在编辑的节点已经存在操作前扩展。';
                    break;
                }
                extension = new Extension();
                extension.type = 'InsertBefore';
                parent.preExtension = extension;
                break;
            case 2:
                if (!parent.replaceable) {
                    msg = '构件不允许正在编辑的节点进行操作替换。';
                    break;
                } else if (parent.replaceExtension) {
                    msg = '正在编辑的节点已经存在操作替换。';
                    break;
                }
                extension = new Extension();
                extension.type = 'Replace';
                parent.replaceExtension = extension;
                break;
            case 3:
                if (!parent.postExtendable) {
                    msg = '构件不允许正在编辑的节点进行操作后扩展。';
                    break;
                } else if (parent.postExtension) {
                    msg = '正在编辑的节点已经存在操作后扩展。';
                    break;
                }
                extension = new Extension();
                extension.type = 'InsertAfter';
                parent.postExtension = extension;
                break;
        }
        if (msg) {
            this.notifyService.warning(msg);
        } else if (extension) {
            extension.isEditing = true;
            extension.id = this.idService.generate();
            extension.position = parent.id;
            extension.tasks = [];
            this.next([...this.commands]);
            (<Command>parent.root).extensions.push(extension);
            (<ITreeNode>parent).expanded = true;
            return extension;
        }
    }

    public addOperation(operation: Operation, parent: Extension | Case, cmpId: string, cmpCode: string) {
        // 创建ExecuteNode
        // const op = item.op;
        const node = new ExecuteNode();
        node.id = this.idService.generate();
        const guid = this.idService.generate();
        node.code = operation.Code + '_' + guid.substr(0, 8);
        node.name = operation.Name;
        node.method = operation.Code;
        node.serviceRefId = cmpId;
        node.serviceName = '';
        node.cmpCode = cmpCode;
        node.extendLevel = OperationExtendLevel.Form;
        if (parent instanceof Extension) {
            node.belongedExt = parent;
        } else if (parent instanceof Case) {
            node.belongedExt = parent.belongedExt;
        }
        node.params = [];
        for (const parameter of operation.Parameters) {
            const param = new ParamConfig();
            param.name = parameter.Code;
            param.shownName = parameter.Name;
            param.value = '';
            param.description = parameter.Description;
            node.params.push(param);
        }
        // 插入节点到最后位置，，可以调整顺序。。
        parent.children.push(node);

        this.next([...this.commands]);
    }

    public addSwitch(parent: ITreeNode) {
        const siblings = parent.children;
        if (siblings.length === 0) {
            this.notifyService.warning('判断节点不能是第一个节点');
            return;
        }

        const node = new SwitchNode();
        node.id = this.idService.generate();
        let i = 1;
        let code: string;
        while (true) {
            code = 'Switch' + i;
            if (!parent.children.find(item => item instanceof SwitchNode && item.code === code)) {
                break;
            }
            ++i;
        }
        node.code = code;
        node.name = '判断' + i;
        node.cases = new Array<Case>();
        if (parent instanceof Extension) {
            node.belongedExt = parent;
        } else if (parent instanceof Case) {
            node.belongedExt = parent.belongedExt;
        }
        parent.children.push(node);

        this.next([...this.commands]);
    }

    public addCase(parent: ITreeNode) {
        const node = new Case();
        node.id = this.idService.generate();
        node.name = '分支' + (parent.children.length + 1);
        node.handlers = new OperationCollection();
        node.belongedExt = (<SwitchNode>parent).belongedExt;
        parent.children.push(node);

        this.next(this.commands);
    }

    public moveNode(node: ITreeNode, direction: 0 /*up*/ | 1 /*down*/) {
        const parent = node.parent;
        if (parent) {
            const arr = parent.children;
            const index = arr.findIndex(item => item === node);
            if ((direction === 0 && index === 0) || (direction === 1 && index === arr.length - 1)) {
                return;
            }
            const destIndex = index + direction * 2 - 1; // 上移+1  下移-1
            if ((destIndex === 0 && node instanceof SwitchNode) || (index === 0 && arr[destIndex] instanceof SwitchNode)) {
                // 第一个节点不能是SwitchNode。
                this.notifyService.warning('判断节点不能是第一个节点');
                return;
            }
            arr[index] = arr[destIndex];
            arr[destIndex] = node;
            this.next([...this.commands]);
        }
    }

    public removeExtNode(node: ITreeNode) {
        const parent = node.parent;

        let msg: string;
        if (!parent) {
            this.showMsg('删除失败，未能正确查找到节点的父节点。', 'error');
            return;
        }

        if (node instanceof Extension && parent instanceof ExecuteNode) {
            switch (node.type) {
                case 'InsertBefore':
                    parent.preExtension = null;
                    break;
                case 'Replace':
                    parent.replaceExtension = null;
                    parent.replaced = false;
                    break;
                case 'InsertAfter':
                    parent.postExtension = null;
                    break;
                default:
                    break;
            }

            const command = <Command>parent.root;
            const extIndex = command.extensions.findIndex(item => item === node);
            if (extIndex !== -1) {
                command.extensions.splice(extIndex, 1);
            }
        } else {
            const siblings = parent.children;
            const index = siblings.findIndex(item => item === node);

            if (index === -1) {
                this.showMsg('删除失败，无法定位此节点在数组中的位置。', 'error');
                return;
            }

            if (index === 0 && siblings[1] instanceof SwitchNode) {
                this.showMsg('无法删除。因为判断节点不能是第一个节点。', 'error');
                return;
            }

            siblings.splice(index, 1);
        }

        this.next([...this.commands]);
    }

    /* #region private methods */

    loadWebcmd() {
        const webcmdInfos = this.domService.getWebCmds();
        const obs = [];

        const sessionId = '';
        const metadataInfo = this.formBasicService.formMetaBasicInfo;
        const relativePath = metadataInfo ? metadataInfo.relativePath : '';
        for (const webcmdInfo of webcmdInfos) {
            const metadataServ = this.metadataService.getMetadata(webcmdInfo.id);
            const ob = metadataServ.pipe(
                catchError(() => {
                    // return throwError(`应用服务器错误：获取元数据${webcmdInfo.name}【${webcmdInfo.id}】失败。`);
                    // 某一个控制器获取失败，不能影响其他控制器的数据组装
                    this.msgService.warning(`获取元数据${webcmdInfo.name}失败，请检查。`);
                    return of(false);
                })
            );
            obs.push(ob);
        }

        let loadingObservable: Observable<FarrisMetadataDto[]>;

        if (obs.length) {
            loadingObservable = forkJoin(obs);
        } else {
            loadingObservable = of([]);
        }

        // 每次加载重新请求。
        return loadingObservable.pipe(
            map(metadataList => {
                return metadataList.filter(item => !!item).map(item => new WebCommandMetadataConvertor().InitFromJobject(JSON.parse(item.content)));
            })
        );
    }

    getViewModelCommandLabel() {
        const codeSet = new Set<string>();
        const domJson = this.domService.getDomJson();
        const viewmodel = domJson.module.viewmodels;
        viewmodel.forEach(viewmodelItem => {
            viewmodelItem.commands.forEach(commandItem => {
                codeSet.add(commandItem.code.toLowerCase());
            });
        })
        return codeSet;
    }

    refreshCommandCodeName(command: Command, viewModelId: string) {

        // collect all command Codes
        // const codeSet = new Set<string>();
        //  modified by wang-xh : code 不区分大小写
        // for (const item of this.commands) {
        //   codeSet.add(item.code.toLowerCase());
        // }
        const codeSet = this.getViewModelCommandLabel();
        // generate code not used
        let i = 1;
        let newCode;
        const vmId = viewModelId.replace(/-/g, '');
        while (true) {
            newCode = vmId + command.code + i;
            if (!codeSet.has(newCode.toLowerCase())) {
                break;
            }
            ++i;
        }
        command.id = this.idService.generate();

        // modified by wang-xh : 解决不同控制器handler重名的问题: 当前VMID+命令code+1
        command.name = command.name + i;
        command.code = vmId + command.code + i;
    }

    private showMsg(message: string, level: 'error' | 'warning' | 'info', title = '系统提示', timeout = 3000) {
        this.notifyService[level](message);
    }
    /* #endregion */
}
